#include "copyThread.h"

#include <QFileInfo>
#include <QMessageBox>
#include <QApplication>
#include <QMutex>
#include <QThread>
#include <iostream>
#include <QFileInfo>
#include <QLockFile>

#include <stdexcept>
#include <limits>

const int CopyThread::m_progressMax = std::numeric_limits<int>::max();
const int CopyThread::m_progressMin = 0;
QMap<QString, QMutexPtr> CopyThread::m_strMutexMap;

CopyThread::CopyThread(const QString &filePathFromPtr, const QString &filePathToPtr, QProgressDialogPtr progressPtr, QObject *parent = 0) try :
    QObject(parent),
    m_fileFrom(filePathFromPtr),
    m_fileTo(filePathToPtr),
    m_progressPtr(progressPtr),
    m_flag(false)
{
    qRegisterMetaType<QProgressDialogPtr>("QProgressDialogPtr");

    if(!connect(this, SIGNAL(showProgressDialog(QProgressDialogPtr)), parent, SLOT(showProgressDialog(QProgressDialogPtr)))){
        throw std::runtime_error(std::string(Q_FUNC_INFO) + "The SIGNAL(showProgressDialog(QProgressDialogPtr)) cannot connect to the SLOT(showProgressDialog(QProgressDialogPtr))\n");
    }

    if(!connect(progressPtr.data(), SIGNAL(canceled()), this, SLOT(stopCopying()))){
        throw std::runtime_error(std::string(Q_FUNC_INFO) + "The SIGNAL(canceled() cannot connect to the SLOT(stopCopying())\n");
    }

    if(!connect(this, SIGNAL(setProgressValue(int)), m_progressPtr.data(), SLOT(setValue(int)))){
        throw std::runtime_error(std::string(Q_FUNC_INFO) + "The SIGNAL(setProgressValue(int)) cannot connect to the SLOT(setValue(int))\n");
    }
}
catch(const std::runtime_error& err){
    std::cout << err.what();
}

CopyThread::~CopyThread()
{
    if(m_strMutexMap[m_fileTo.fileName()].isNull()){
        m_strMutexMap.remove(m_fileTo.fileName());
    }
}

void CopyThread::copyFile()
{
    try{
        QMap<QString, QMutexPtr>::iterator iter = m_strMutexMap.find(m_fileTo.fileName());

        //if key not found
        if(iter == m_strMutexMap.end()){
            m_strMutexMap.insert(m_fileTo.fileName(), QSharedPointer<QMutex>(new QMutex));
        }
        QMutexLocker locker(m_strMutexMap[m_fileTo.fileName()].data());
        doCopy();
    }
    catch(const std::runtime_error& err){
        std::cerr << err.what();
    }
}

void CopyThread::doCopy()
{
    m_fileFrom.open(QFile::ReadOnly);
    m_fileTo.open(QFile::WriteOnly);

    m_progressPtr->setLabelText("Copying from \" " + m_fileFrom.fileName() + "\" to \"\n" + m_fileTo.fileName() + "\" .");
    m_progressPtr->setMaximum(m_progressMax);
    m_progressPtr->setMinimum(m_progressMin);
    emit bytesWrittenProgress();

    const int updateTime = 100;
    m_timer.start(updateTime);

    if(!connect(&m_timer, SIGNAL(timeout()), this, SLOT(bytesWrittenProgress()), Qt::DirectConnection)){
        throw std::runtime_error(std::string(Q_FUNC_INFO) + "The SIGNAL(setProgressLabelText(QString)) cannot connect to the SLOT(setLabelText(QString))\n");
    }

    static const int KB = 1024;
    std::vector<char> array(KB);
    array.assign(array.size(), 0);

    qint64 fileSize = m_fileFrom.size();
    qint64 wasReaded = 0;

    emit showProgressDialog(m_progressPtr);

    for(qint64 i = 0; i < fileSize && !m_flag; ){
        wasReaded = m_fileFrom.read(&array[0], array.size());
        if(wasReaded < 1){
            std::cerr << Q_FUNC_INFO << "Read error!";
            break;
        }

        m_mutexFileWrite.lock();
        i += m_fileTo.write(&array[0], wasReaded);
        m_mutexFileWrite.unlock();
        array.assign(array.size(), 0);
    }

    m_fileFrom.close();
    m_fileTo.close();

}

void CopyThread::run()
{
    try {
        copyFile();
    } catch (const std::runtime_error& err) {
        std::cerr << err.what();
    }
}

void CopyThread::stopCopying()
{
    m_flag = true;
}

void CopyThread::bytesWrittenProgress()
{
    const qint64 sizeFrom = m_fileFrom.size();
    m_mutexFileWrite.lock();
    const qint64 bytesWritten = m_fileTo.size();
    m_mutexFileWrite.unlock();
    if(sizeFrom == 0){
        return;
    }

    double koeff = static_cast<double>(m_progressMax) / static_cast<double>(sizeFrom);
    double bytes = static_cast<double>(bytesWritten) * koeff;

    emit setProgressValue(static_cast<int>(bytes));

    if(sizeFrom == bytesWritten){
        emit setProgressValue(m_progressMax);
        m_timer.stop();
    }
}


